
#include "klog.h"
#include "knxengine.h"
#include "frame_interface.h"
#include "serversetting.h"
#include "cmei.h"
#include "knxenvagent.h"
#include <QTimer>

#define SERVER_INIT_STATE   -1

KNXEngine::KNXEngine(QObject *parent):
    QObject(parent)
{
    self = QString("KNXEngine: %1l").arg((qlonglong)this);
    //udpSend.bind(QHostAddress(ServerSetting::defaultSetting()->KnxIpAddress));
    ipKnxBoardcast = QHostAddress(KNX_MULTICAST_ADDRESS);
    isConnected = false;
    busMonitor_data_len = 0;

    currentState = SERVER_INIT_STATE;

    set_cid(INVALID_CHANNEL_ID);

    connect(&beatTimer, SIGNAL(timeout()), this, SLOT(ipsHeartBeat()));
    connect(&connectTimer, SIGNAL(timeout()), this, SLOT(autoConnectKnx()));
    //connect(&tunnellingTimer, SIGNAL(timeout()), this, SLOT(tunneling_steps_check()));
}

void KNXEngine::initNetWorkEnv(QString serverIp, QString localIpAddr,quint16 cntPort,quint16 dataPort,KNX_CONNECT_TYPE type)
{
    this->connectType = type;
    this->server_port = SERVER_PORT;

    this->control_port = cntPort;
    this->data_port = dataPort;

    this->ipV4Server = QHostAddress(serverIp);
    this->ipV4local = QHostAddress(localIpAddr);

    KDEBUG << " init server ip: " << serverIp << " use local ip:" << localIpAddr;

}

bool KNXEngine::isConnectToServer()
{
    return isConnected;
}

KNXEngine::~KNXEngine()
{
    this->udpRcv_cnt.close();
    this->udpRcv_data.close();
    this->udpBordcast.close();
    this->udpSend.close();

    unRegisterAll();
}

bool KNXEngine::readyWork()
{
    int maxTry = 3;
    int failTimeForEach = 0;

    if(connectType == KNX_IPR)
    {
        qDebug() << "!Start IPR KNX: ip " << this->ipV4local.toString() << "& port " << server_port;
        udpBordcastSend.bind(this->ipV4local);

        /*this->ipV4local QHostAddress("0.0.0.0")*/
        if(!udpBordcast.bind(QHostAddress("0.0.0.0"),
                             server_port))
        {
            KDEBUG << "udpBordcast socket bind fail " << server_port ;
            return false;
        }

        if(!udpBordcast.joinMulticastGroup(QHostAddress(KNX_MULTICAST_ADDRESS)))
            qDebug() << "IPR INIT, joinMulticastGroup fail";

        connect(&udpBordcast,SIGNAL(readyRead()),this,SLOT(readDataBordcast()));
    }
    else
    {
        //ips need to bind 2 ports
        while(1)
        {
            KnxEnvAgent::searchPort(&this->control_port);
            if(udpRcv_cnt.bind(QHostAddress("0.0.0.0"),this->control_port))
            //if(udpRcv_cnt.bind(QHostAddress("192.168.5.128"),this->control_port))
            {
                //this->control_port = udpRcv_cnt.peerPort();
                break;
            }
            else if(failTimeForEach ++ > maxTry)
            {
                KDEBUG << "udpRcv_cnt bind fail, some bug ? ";
                return false;
            }
        }

        while(1)
        {
            KnxEnvAgent::searchPort(&this->data_port);
            if(udpRcv_data.bind(QHostAddress("0.0.0.0"),this->data_port))
            //if(udpRcv_data.bind(QHostAddress("192.168.5.128"),this->data_port))
            {
                //this->data_port = udpRcv_data.peerPort();
                break;
            }
            else if(failTimeForEach ++ > maxTry)
            {
                KDEBUG << "udpRcv_data bind fail, some bug ? ";
                return false;
            }
        }
    }

    connect(&udpRcv_cnt,SIGNAL(readyRead()),this,SLOT(readControlSocket()));
    connect(&udpRcv_data,SIGNAL(readyRead()),this,SLOT(readDataSocket()));

    return true;
}

void KNXEngine::startWork()
{
    if(connectType == KNX_IPR)
    {
        //this->searchKnxServer();
        isConnected = true;
        updateObjs();
    }
    else
    {
        connectToKnx();
        connectTimer.start(10*1000);
    }
}

void KNXEngine::stopWork()
{
    if(connectType == KNX_IPS)
    {
        if(isConnectToServer())
        {
            stopHeartBeat();
            disconnectFromKnx();
        }
        //try to wait the disconenct response
        processEvents(2000);
    }
    else
    {
        udpBordcast.leaveMulticastGroup(QHostAddress(KNX_MULTICAST_ADDRESS));
    }
}

void KNXEngine::readControlSocket()
{
    QByteArray cmdBytes;

    if (udpRcv_cnt.hasPendingDatagrams())
    {
        cmdBytes.resize(udpRcv_cnt.pendingDatagramSize());
        udpRcv_cnt.readDatagram(cmdBytes.data(),cmdBytes.size());

        //qDebug() << "udpRcv_cnt get data: "<< cmdBytes.size() ;

        data_analyze((_u8 *)cmdBytes.data(),cmdBytes.size());
    }

}

void KNXEngine::readDataSocket()
{
    QByteArray cmdBytes;

    if (udpRcv_data.hasPendingDatagrams())
    {
        cmdBytes.resize(udpRcv_data.pendingDatagramSize());
        udpRcv_data.readDatagram(cmdBytes.data(),cmdBytes.size());

        //qDebug() << "udpRcv_data get data: "<< cmdBytes.size() ;

        data_analyze((_u8 *)cmdBytes.data(),cmdBytes.size());
    }
}

void KNXEngine::readDataBordcast()
{
    QByteArray cmdBytes;
    if (udpBordcast.hasPendingDatagrams())
    {
        cmdBytes.resize(udpBordcast.pendingDatagramSize());
        udpBordcast.readDatagram(cmdBytes.data(),cmdBytes.size());

        data_analyze((_u8 *)cmdBytes.data(),cmdBytes.size());
    }
}

void KNXEngine::udpSendToKnx(char *data,quint32 len)
{
    udpSend.writeDatagram(data,len,ipV4Server,server_port);
    udpSend.flush();
    udpSend.waitForBytesWritten(100);
}

void KNXEngine::udpBordcastSendTo(char *data,quint32 len)
{
    udpBordcastSend.writeDatagram(data,len,ipKnxBoardcast,server_port);
    udpBordcastSend.flush();
}


bool KNXEngine::registeObject(void *devObj, void *userdata)
{    
    DEVICE_OBJECT *obj = (DEVICE_OBJECT *)devObj;

    //KDEBUG << QString("registeObject: dest addrss") << obj->_address.data() << obj->_data_type;

    obj->_user_data = userdata;

    _registersMap.insertMulti(QByteArray((char *)obj->_ga_ia,2) ,obj);

    return true;
}

//根据地址和控件来取消
bool KNXEngine::unRegisterObject(void *devObj,void *userdata)
{
    DEVICE_OBJECT * objs = (DEVICE_OBJECT *)devObj;

    QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator i = _registersMap.begin();
    while (i != _registersMap.end()) {
        QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator prev = i;
        ++i;
        if (prev.key() == QByteArray((char *)objs->_ga_ia,2))
        {
            DEVICE_OBJECT *o = prev.value();
            //服务器端,控件是不存在的,可能为 NULL
            if(o && o == objs)
            {
                free(o);
                _registersMap.erase(prev);
                return true;
            }
            else
                continue;
        }
    }

    return false;
}

void KNXEngine::unRegisterAll()
{
    QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator i = _registersMap.begin();
    while (i != _registersMap.end()) {
        QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator prev = i;
        ++i;

        DEVICE_OBJECT *o = prev.value();
        free(o);
    }

    _registersMap.clear();
}

void KNXEngine::updateObjs()
{
    DEVICE_OBJECT * objs = NULL;

    QMultiMap<QByteArray, DEVICE_OBJECT *>::iterator it ;
    for(it = _registersMap.begin(); it != _registersMap.end(); it++)
    {
        objs = it.value();
        getValue(objs->_address, objs->_data_type);
        QThread::msleep(100);
    }
}

void KNXEngine::search_response(_u8 *data,int msg_len)
{
    _u32 iprAddr;

    if(msg_len<39)
    {
        KDEBUG << "search_response: msg len is too small " << msg_len ;
        return ;
    }

    memset(&g_knx_server,0,sizeof(KNX_SERVER));

    memcpy(&g_knx_server._ip_addr, &data[8], sizeof(g_knx_server._ip_addr));
    memcpy(&g_knx_server._port, &data[12],sizeof(g_knx_server._port) );
    memcpy(&g_knx_server._device_status, &data[17], sizeof(g_knx_server._device_status));
    memcpy(&g_knx_server._individual_addr, &data[18],sizeof(g_knx_server._individual_addr));
    memcpy(&g_knx_server._serial_num, &data[22], sizeof(g_knx_server._serial_num));
    memcpy(&g_knx_server._multicast_addr, &data[28],sizeof(g_knx_server._multicast_addr) );
    memcpy(&g_knx_server._mac_addr, &data[32],sizeof(g_knx_server._mac_addr));
    memcpy(&g_knx_server._device_friendly_name, &data[38],sizeof(g_knx_server._device_friendly_name));

    qDebug() << "search_response: KNX device - " << g_knx_server._device_friendly_name
           << " ip "<< quint32(g_knx_server._ip_addr[0]) << quint32(g_knx_server._ip_addr[1]) << quint32(g_knx_server._ip_addr[2]) << quint32(g_knx_server._ip_addr[3])
           << "port " << quint16(g_knx_server._port[0]<<8| g_knx_server._port[1]);

    _int32 detectMode = KNX_NETWORK_MODE_IPR;
    if(NULL != strstr(g_knx_server._device_friendly_name, "IPS") ||
      NULL != strstr(g_knx_server._device_friendly_name, "ips"))
    {
       detectMode = KNX_NETWORK_MODE_IPS;
    }

    KDEBUG << QString("notice: user setting mode is %1, detect Device is %2")
              .arg(connectType == KNX_NETWORK_MODE_IPR? "IPR":"IPS")
              .arg(detectMode == KNX_NETWORK_MODE_IPR? "IPR":"IPS");

    iprAddr = (g_knx_server._ip_addr[3] << 24) | (g_knx_server._ip_addr[2] << 16) |
    (g_knx_server._ip_addr[1] << 8) | g_knx_server._ip_addr[0] ;

    QString ip = QString("%1.%2.%3.%4").arg(g_knx_server._ip_addr[0])
                            .arg(g_knx_server._ip_addr[1])
                            .arg(g_knx_server._ip_addr[2])
                            .arg(g_knx_server._ip_addr[3]);

    if(this->connectType == KNX_NETWORK_MODE_IPS )
    {
       if (ip != ipV4Server.toString()) {
           KDEBUG << "error: user setting ip is not equal to the detect Device ip: " << ip << ipV4Server.toString();
       }
    }
    else
    {
        setKnxServerIp(ip);
        isConnected = true;
       //connect_request();
    }


}

void KNXEngine::set_cid(_u8 cid)
{
    g_channel_id = cid;
}

_u8 KNXEngine::get_cid()
{
    _u8 cid = 0;

    cid = g_channel_id;

    return cid;
}

_int32 KNXEngine::connect_error_handle(_u8 errcode)
{
    if (errcode != E_NO_ERROR)
    {
        //reconnect timer is running
        if(connectTimer.isActive())
            return 0;

        //10 sconds
        int restartTime = 1000*10;

        if(E_NO_MORE_CONNECTIONS == errcode)
            restartTime = 1000*20;

        stopHeartBeat();
        isConnected = false;

        KDEBUG << "KNX Engine errors: " << errcode << ", will reconnect later..." ;

        autoConnectKnx();
        connectTimer.start(restartTime);
        //QTimer::singleShot(restartTime, this, SLOT(connectToKnx()));

    }

    return SUCCESS;
}

void KNXEngine::autoConnectKnx()
{
    disconnectFromKnx();
    // !!!the wait time should less than the timer interval
    processEvents(2000);

    if(currentState == SERVER_INIT_STATE)
        emit engineErrorNotify("设备连接无响应,网络设置是否正确?",SERVER_INIT_STATE);
    this->connectToKnx();
}

void KNXEngine::connect_response(_u8 *recv_buf,int msg_len)
{
    _u8 errorcode = recv_buf[7];
    if(errorcode == E_NO_ERROR)
    {
        KDEBUG << "connect_response: get cid " << (quint32)recv_buf[6];
        startHeartBeat();
        connectTimer.stop();
        isConnected = true;

        set_cid(recv_buf[6]);
        reset_tunnelling_sc();
        tunneling_sync_finish();
        updateObjs();

        //emit engineErrorNotify("设备连接正常!",E_NO_ERROR);
    }
    else
    {
        KDEBUG << "!connect_response: error " << (quint32)errorcode;

        if(errorcode == E_NO_MORE_CONNECTIONS)
            emit engineErrorNotify("连接失败,设备被占用!",errorcode);
        else
            emit engineErrorNotify("通讯出错,将自动重连,请等待!",errorcode);
    }

}

void KNXEngine::ipsHeartBeat()
{
    QTime now = QTime::currentTime();
    if(now.secsTo(heartBeat) > 3*HEART_BEAT_INTERVAL)
    {
        connect_error_handle(E_CONNECT_STATE_TIMEOUT);
    }
    else
    {
        connect_state();
    }
}

void KNXEngine::startHeartBeat()
{
    heartBeat = QTime::currentTime();
    beatTimer.start(HEART_BEAT_INTERVAL*1000);
}

void KNXEngine::stopHeartBeat()
{
    beatTimer.stop();
}

void KNXEngine::connectToKnx()
{
    _u32 ip = ipV4local.toIPv4Address();
    _u8 buffer[26] = {0};

    // Connect Header --------------------------------------------------

    buffer[0] = 0x06;     // header size = 6 for version 1.0 of protocol
    buffer[1] = 0x10;     // protocol version 1.0
    buffer[2] = 0x02;     // connect request (2 bytes)
    buffer[3] = 0x05;     //  -- '' --
    buffer[4] = 0x00;     // full size of packet (2 bytes) = v1.0 header size (6) + client + server HPAI (16 -- 8 bytes per HPAI) + CRI size (4) == 26 == 0x1A
    buffer[5] = 0x1A;

    // Client HPAI -----------------------------------------------------

    buffer[6] = 0x08;     // client HPAI structure len (8 bytes)
    buffer[7] = 0x01;     // protocol type (1 = UDP)
    buffer[8] = QT_IP_BYTE_0(ip);         // Client IP Address (4 bytes)
    buffer[9] = QT_IP_BYTE_1(ip);         //  -- '' --
    buffer[10] = QT_IP_BYTE_2(ip);        //  -- '' --
    buffer[11] = QT_IP_BYTE_3(ip);        //  -- '' --
    buffer[12] = PORT_BYTE_HI(control_port);   // Client UDP Port  (2 bytes)
    buffer[13] = PORT_BYTE_LOW(control_port);   //  -- '' --

    // Server HPAI ------------------------------------------------------

    buffer[14] = 0x08;     // Server HPAI structure len (8 bytes)
    buffer[15] = 0x01;     // protocol type (1 = UDP)
    buffer[16] = QT_IP_BYTE_0(ip);          // Server IP Address (4 bytes)
    buffer[17] = QT_IP_BYTE_1(ip);          //  -- '' --
    buffer[18] = QT_IP_BYTE_2(ip);         //  -- '' --
    buffer[19] = QT_IP_BYTE_3(ip);          //  -- '' --
    buffer[20] = PORT_BYTE_HI(data_port);         // Server Port (2 bytes)
    buffer[21] = PORT_BYTE_LOW(data_port);           //  -- '' --

    // CRI ---------------------------------------------------------------

    buffer[22] = 0x04;      // Structure len (4 bytes)
    buffer[23] = 0x04;      // Tunnel Connection
    buffer[24] = 0x02;      // KNX Layer (Tunnel Link Layer)
    buffer[25] = 0x00;      // Reserved

    udpSendToKnx((char *)buffer,26);

}

void KNXEngine::disconnectFromKnx()
{
    _u32 ip = ipV4local.toIPv4Address();
    _u8 cid = get_cid();
    _u16 port = control_port;

    if (cid == INVALID_CHANNEL_ID) {
        return;
    }

    KDEBUG << "disconnectFromKnx: cid is " << cid;

    char buffer[16] = {0};

    // Connect Header --------------------------------------------------

    buffer[0] = 0x06;     // header size = 6 for version 1.0 of protocol
    buffer[1] = 0x10;     // protocol version 1.0

    buffer[2] = 0x02;     // disconnect request (2 bytes)
    buffer[3] = 0x09;     //  -- '' --

    buffer[4] = 0x00;     //
    buffer[5] = 0x10;     // 16 bytes

    // Client HPAI -----------------------------------------------------

    buffer[6] = cid;      // CID
    buffer[7] = 0x00;     // reserver
    buffer[8] = 0x08;     //length
    buffer[9] = 0x01;     //V4_udp
    buffer[10] = QT_IP_BYTE_0(ip);        //  -- '' --
    buffer[11] = QT_IP_BYTE_1(ip);        //  -- '' --
    buffer[12] = QT_IP_BYTE_2(ip);   // Client UDP Port  (2 bytes)
    buffer[13] = QT_IP_BYTE_3(ip);   //  -- '' --
    buffer[14] = PORT_BYTE_HI(port);
    buffer[15] = PORT_BYTE_LOW(port);

    udpSendToKnx(buffer,16);
}

void KNXEngine::connect_state()
{
    _u32 ip = ipV4local.toIPv4Address();
    _u16 port = control_port;

    // EIBnet IP Header ----------------------------------------
    _u8 writebuffer[24] = {0};

    writebuffer[0] = 0x06;      // Header size: 6 bytes
    writebuffer[1] = 0x10;      // EIBnet IP Version
    writebuffer[2] = 0x02;      // State Request (2 bytes)
    writebuffer[3] = 0x07;      //  -- '' --
    writebuffer[4] = 0x00;      // size (2 bytes)
    writebuffer[5] = 0x10;      //  -- '' --

    // Connection Header ---------------------------------------

    writebuffer[6] = get_cid();
    writebuffer[7] = 0x0;

    // Client HPAI -----------------------------------------------------

    writebuffer[8] = 0x08;     // client HPAI structure len (8 bytes)
    writebuffer[9] = 0x01;     // protocol type (1 = UDP)
    writebuffer[10] = QT_IP_BYTE_0(ip);         // Client IP Address (4 bytes)
    writebuffer[11] = QT_IP_BYTE_1(ip);         //  -- '' --
    writebuffer[12] = QT_IP_BYTE_2(ip);        //  -- '' --
    writebuffer[13] = QT_IP_BYTE_3(ip);        //  -- '' --
    writebuffer[14] = PORT_BYTE_HI(port);   // Client UDP Port  (2 bytes)
    writebuffer[15] = PORT_BYTE_LOW(port);   //  -- '' --

    udpSendToKnx((char *)writebuffer,16);
}

void KNXEngine::connect_state_response(_u8 *recv_buf,int msg_len)
{
    if(msg_len<8)
    {
        KDEBUG << "connect_state_response: too short message " << msg_len;
        return ;
    }

    _u8 cid = recv_buf[6];
    _u8 errcode = recv_buf[7];

    KDEBUG << "connect_state_response: cid  error" << cid << errcode;

    heartBeat = QTime::currentTime();

    if (errcode != E_NO_ERROR && connectType == KNX_IPS) {
        KDEBUG << "connect_state_response: get error - "<< errcode
               <<  " using cid is - "<< g_channel_id;

        //connect_error_handle(errcode);
    }
}

void KNXEngine::disconnect_response(_u8 *msg,int msg_len)
{
    _u8 channelId = msg[6];      // CID
    KDEBUG << "disconnect_response: cid is " << channelId;
    set_cid(INVALID_CHANNEL_ID);
    isConnected = false;
}

void KNXEngine::setKnxServerIp(QString ip)
{
    ipV4Server = QHostAddress(ip);
}

void KNXEngine::data_analyze(_u8 *msg,int msg_len)
{
    _u16 recv_cmd = frame_get_cmd(msg);
    _u8 *dest_addr = NULL;
    _u8 *device_data = NULL, *device_value = NULL,value_len = 0;

    _u8 operation_type = 0;
    char str_group_addr[16] = {0};

    switch (recv_cmd) {
        case SEARCH_RESPONSE:
        {
            search_response(msg,MAX_RECV_MSG_LEN);
            break;
        }
        case CONNECTION_RESPONSE:
        {
            connect_response(msg,MAX_RECV_MSG_LEN);
            break;
        }
        case CONNECTIONSTATE_RESPONSE:
        {
            connect_state_response(msg,8);
            break;
        }
        case DISCONNECT_REQUEST:
        {
            break;
        }
        case DISCONNECT_RESPONSE:
        {
            disconnect_response(msg,MAX_RECV_MSG_LEN);
            break;
        }
        case TUNNEL_ACK:
        {
            tunneling_ack_from_knx(msg,MAX_DATA_BYTES_LEN);
            break;
        }
        case TUNNEL_REQUEST:
        {
            //in IPS mode, client will also recv tunnelling request msg
            device_data = tunneling_request_from_knx(msg,msg_len);

            if(cmei_get_control1(device_data) == L_Data_con || cmei_get_control1(device_data) == L_Data_ind)
            {
                //skip msg & addtion code
                device_data += 2;

                cmei_get_dest_addr(device_data,&dest_addr);

                cmei_get_string_group_address(str_group_addr,dest_addr);
                QByteArray strAddr((char *)dest_addr,2);

                KDEBUG << QString("Tunnelling_INDICATION: dest addrss") << str_group_addr;

                QList<DEVICE_OBJECT *> regist_list = _registersMap.values(strAddr);
                //print_data(str_group_addr,device_data,16);
                if(regist_list.size()>0)
                {
                    DEVICE_OBJECT *obj = NULL;
                    for (int i = 0; i < regist_list.size(); ++i) {
                        obj = (DEVICE_OBJECT *)regist_list.at(i);

                        cmei_get_device_data(device_data,&device_value);

                        routing_indication_recv_analyze(device_value,obj->_data_type,&operation_type,
                                                        &device_value,&value_len);
                        if(operation_type == APCI_VALUE_RESP ||
                           (operation_type == APCI_VALUE_WRITE && !cmei_is_source_address_empty(device_data)))
                        {
                            message_device_data_handle(obj,device_value,value_len);
                        }
                    }

                }
            }

            break;
        }
        case ROUTING_INDICATION:
        {
            device_data = &msg[8];

            if(!is_group_address(cmei_get_control2(device_data)))
                 break;

            cmei_get_dest_addr(device_data,&dest_addr);

            cmei_get_string_group_address(str_group_addr,dest_addr);

            KDEBUG << QString("ROUTING_INDICATION: dest addrss") << str_group_addr;

            if(busMonitor_data_len > 0)
            {
                cmei_get_device_data(device_data,&device_value);
                routing_indication_recv_analyze(device_value,TWO_BYTES_VALUE,&operation_type,
                                                &device_value,&value_len);

                QString gAddr = QString((char *)str_group_addr);
                QByteArray routingData = QByteArray((char *)device_value,busMonitor_data_len);
                emit routingMonitorNotify(gAddr,routingData);
            }
            else
            {
                QByteArray strAddr((char *)dest_addr,2);
                QList<DEVICE_OBJECT *> regist_list = _registersMap.values(strAddr);

                if(regist_list.size())
                {
                    print_data(str_group_addr,device_data,16);

                    DEVICE_OBJECT *obj = NULL;
                    for (int i = 0; i < regist_list.size(); ++i) {
                        obj = (DEVICE_OBJECT *)regist_list.at(i);

                        cmei_get_device_data(device_data,&device_value);
                        routing_indication_recv_analyze(device_value,obj->_data_type,&operation_type,
                                                        &device_value,&value_len);
                        if(operation_type == APCI_VALUE_RESP ||
                           (operation_type == APCI_VALUE_WRITE && !cmei_is_source_address_empty(device_data)))
                        {
                            message_device_data_handle(obj,device_value,value_len);
                        }
                    }
                }
            }

            break;
        }

        default:
        {
        KDEBUG << QString("revc_cmd is 0x%1 0x%2 ").arg(msg[2],0,16).arg(msg[3],0,16);
            break;
        }
    }
}

void KNXEngine::searchKnxServer()
{
    _u32 ip = ipV4local.toIPv4Address();
    _u16 port = control_port;

    _u8 buffer[14] = {0};

    // Connect Header --------------------------------------------------

    buffer[0] = 0x06;     // header size = 6 for version 1.0 of protocol
    buffer[1] = 0x10;     // protocol version 1.0

    buffer[2] = 0x02;     // search_request(2 bytes)
    buffer[3] = 0x01;     //  -- '' --

    buffer[4] = 0x00;     //
    buffer[5] = 0x0e;     // 14 bytes

    // Client HPAI -----------------------------------------------------

    buffer[6] = 0x08;     //length
    buffer[7] = 0x01;     //V4_udp
    buffer[8] = QT_IP_BYTE_0(ip);        //  -- '' --
    buffer[9] = QT_IP_BYTE_1(ip);        //  -- '' --
    buffer[10] = QT_IP_BYTE_2(ip);   // Client UDP Port  (2 bytes)
    buffer[11] = QT_IP_BYTE_3(ip);   //  -- '' --
    buffer[12] = PORT_BYTE_HI(port);
    buffer[13] = PORT_BYTE_LOW(port);

    udpBordcastSendTo((char *)buffer,14);

}

void KNXEngine::setValue(QByteArray address, int type, QByteArray value)
{
    _u8 temp_buffer[2];
    cmei_set_group_address(temp_buffer,address);

    if (connectType == KNX_IPS) {
        ips_send_data(temp_buffer, GROUP_ADDR, type,(_u8 *)value.data(),value.size());
    }else{
        ipr_send_data(temp_buffer, GROUP_ADDR, type,(_u8 *)value.data(),value.size());
    }
}

void KNXEngine::getValue(QByteArray address, int type)
{
    _u8 temp_buffer[2];
    qDebug() << "KNX update: " << address;
    cmei_set_group_address(temp_buffer,address);

    if (connectType == KNX_IPS)
    {
        ips_read_status(temp_buffer);
    }
    else
    {
        ipr_read_status(temp_buffer);
    }
}


_int32 KNXEngine::message_device_data_handle(DEVICE_OBJECT *data,_u8 *value,_u8 value_len)
{
    if (data!=NULL && value!=NULL) {
        QByteArray valueByte;

        switch (data->_data_type) {
            case ONE_BIT_VALUE:
            {                
                valueByte = QByteArray((char *)value,1);
                KDEBUG << "onebit_callback:" << data->_address << " = " << valueByte.toUInt();
                //emit engineValueNotify(data->_address,ONE_BIT_VALUE,valueByte,data->_user_data);

                break;
            }

            case ONE_BYTE_VALUE:
            case TWO_BYTES_VALUE:
                //case BYTES_VALUE:
            {
                //skip APCI/APEI
                value ++;
                value_len --;

                valueByte = QByteArray((char *)value,value_len);
                KDEBUG << "bytes_callback:" <<  data->_address << "len = " << value_len;
                //emit engineValueNotify(data->_address,data->_data_type,QByteArray((char *)value,value_len),data->_user_data);

                break;

            }

            default:
            {
                KDEBUG << "recv_msg_handle: error data type " << data->_data_type;
                return -1;

            }

        }

        emit engineValueNotify((void *)data,valueByte,data->_user_data);
    }

    return 0;
}


_int32 KNXEngine::ips_read_status(_u8 addr[2])
{
    _u8 data = APCI_VALUE_READ;
    _u8 channel_id = get_cid();
    int retry = 3;

    if (!isConnectToServer()) {
        KDEBUG << "KNX: network is not ready!";
        return -1;
    }

    _u8 msg_len = onebit_tunnelling_request(channel_id,common_buffer,64,addr,data);

    tunneling_sync_start();

    udpSendToKnx((char *)common_buffer,msg_len);

ReSend:

    if(!tunneling_sync_wait())
    {
        if(retry -- > 0)
        {
            //send 3 more time
            for(int i=0;i<3;i++)
            {
                udpSendToKnx((char *)common_buffer,msg_len);
                processEvents(20);
            }
            goto ReSend;
        }
        else
        {
            connect_error_handle(E_CONNECT_TUNNELLING_TIMEOUT);
            return FAIL;
        }
    }

    return SUCCESS;
}

_int32 KNXEngine::ipr_read_status(_u8 addr[2])
{
    _u8 data = APCI_VALUE_READ;
    _u8 msg_len = bytes_routing(common_buffer,32,addr,&data,1);

    udpBordcastSendTo((char *)common_buffer,msg_len);

    return SUCCESS;
}


_int32 KNXEngine::ipr_send_data(_u8 addr[2],_u8 addr_type,_u8 value_type,_u8 *data,_u32 len)
{
    _int32 msg_len = 0;
    common_data_buffer[0] = APCI_VALUE_WRITE;

    if(len>MAX_DATA_BYTES_LEN)
    {
        KDEBUG << "ipr_send_data len is too large " << len;
        return -1;
    }

    if (value_type == ONE_BIT_VALUE) {
        common_data_buffer[0] |= (*data & 0x1);
        msg_len = onebit_routing(common_buffer,64,addr,common_data_buffer[0]);
    }
    else
    {
        memccpy(&common_data_buffer[1], data, len, MAX_DATA_BYTES_LEN);
        msg_len = bytes_routing(common_buffer,128,addr,common_data_buffer,len+1);
    }

    udpBordcastSendTo((char *)common_buffer,msg_len);

    return SUCCESS;
}

_int32 KNXEngine::ips_send_data(_u8 addr[2],_u8 addr_type,_u8 value_type,_u8 *data,_u32 len)
{
    _int32 msg_len = 0 , retry = 3;
    common_data_buffer[0] = APCI_VALUE_WRITE;

    if(len>MAX_DATA_BYTES_LEN)
    {
        KDEBUG << "ipr_send_data len is too large " << len;
        return -1;
    }

    if (!isConnectToServer()) {
        KDEBUG << "KNX: network is not ready!";
        return -1;
    }

    // operation was not finish last time
    if (!tunneling_sync_wait())
    {
        return FAIL;
    }

    _u8 channel_id = get_cid();

    if (value_type == ONE_BIT_VALUE) {
        common_data_buffer[0] |= (*data & 0x1);
        msg_len = onebit_tunnelling_request(channel_id,common_buffer,64,addr,common_data_buffer[0]);
    }
    else
    {
        memccpy(&common_data_buffer[1], data, len, MAX_DATA_BYTES_LEN);
        msg_len = bytes_tunnelling_request(channel_id,common_buffer,128,addr,common_data_buffer,len+1);
    }

    tunneling_sync_start();

    udpSendToKnx((char *)common_buffer,msg_len);

ReSend:

    if(!tunneling_sync_wait())
    {
        if(retry -- > 0)
        {
            //send 3 more time
            for(int i=0;i<3;i++)
            {
                udpSendToKnx((char *)common_buffer,msg_len);
                processEvents(20);
            }
            goto ReSend;
        }
        else
        {
            connect_error_handle(E_CONNECT_TUNNELLING_TIMEOUT);
            return FAIL;
        }
    }
    return SUCCESS;
}
